Solutions/Lookout/Data Connectors/LookoutAPISentinelConnector/mes_request.py (186 lines of code) (raw):

''' Module MESRequest to authenticate the Connector plug-in and collect threat events from the Lookout RISK API. ''' import json import sys import logging import requests import os from .azuresecret_handler import AzureSecretHandler class MESRequest: ''' Class MESRequest to authenticate the plug-in and collect threat events from the Lookout RISK API. ''' def __init__(self, api_domain, ent_name, api_key, vault_uri, key_index=0): # static fields self.api_domain = api_domain self.ent_name = ent_name self.api_key = api_key # populate dynamic variables from the Azure Key Vault self.az_kv = AzureSecretHandler(vault_uri) self.access_token = self.az_kv.get_secret("AccessToken") self.refresh_token = self.az_kv.get_secret("RefreshToken") self.stream_position = self.az_kv.get_secret("StreamPosition") self.is_valid = self.az_kv.get_secret("IsValid") self.retry_counter = self.az_kv.get_secret("AuthRetryCounter") if not self.retry_counter: self.retry_counter = 1 else: self.retry_counter = int(self.retry_counter) if not self.is_valid: self.is_valid = "YES" #For very first time, vault doesn't have any stream position saved, so start from 0 if not self.stream_position: self.stream_position = 0 self.stale_token_errors = ["REVOKED_REFRESH_TOKEN", "EXPIRED_TOKEN"] def refresh_header(self): '''Format request headers''' return { "accept": "application/json", "content-type": "application/x-www-form-urlencoded", "cache-control": "no-cache" } def header(self, token): '''Format request headers''' return { "accept": "application/json", "authorization": "Bearer {}".format(token), "content-type": "application/x-www-form-urlencoded", "cache-control": "no-cache" } def refresh_oauth(self): ''' Refresh access token using the refresh token - Stores the new access token in the Azure Vault and in this object - If the refresh token has expired, requests a new refresh and access token and stores them in the Azure Vault and in this object ''' try: response = requests.post(self.api_domain + "/oauth/token", data="refresh_token={}&grant_type=refresh_token" .format(self.refresh_token), headers=self.refresh_header()) response_content = json.loads(response.text) if 'access_token' in response_content: self.access_token = response_content['access_token'] self.az_kv.set_secret("AccessToken", self.access_token) self.az_kv.set_secret("IsValid", "YES") self.az_kv.set_secret("AuthRetryCounter", 1) else: # if the refresh failed, request brand new API credentials response = requests.post(self.api_domain + "/oauth/token", data="grant_type=client_credentials", headers=self.header(self.api_key)) response_content = json.loads(response.text) if 'access_token' in response_content: self.access_token = response_content['access_token'] self.refresh_token = response_content['refresh_token'] self.az_kv.set_secret("AccessToken", self.access_token) self.az_kv.set_secret("RefreshToken", self.refresh_token) self.az_kv.set_secret("IsValid", "YES") self.az_kv.set_secret("AuthRetryCounter", 1) else: if response_content['error'] and response_content['error'] == 'invalid_client': # Set flag to avoid unwanted retries in case of invalid key/client self.retry_counter = int(self.retry_counter) + 1 self.az_kv.set_secret("AuthRetryCounter", self.retry_counter) if int(self.retry_counter) >= 10: self.az_kv.set_secret("IsValid", "NO") logging.error("Your Lookout application key has expired. " + "Please get a new key and set up this connector app again.\n" + "Go to https://mtp.lookout.com and generate a new key by " + "navigating to System => Application Keys.") logging.error("Exiting...") sys.exit(1) except requests.exceptions.ProxyError as e: logging.error("Cannot connect to proxy. Remote end closed connection without response") except requests.exceptions.RequestException as e: logging.error(e) def get_oauth(self): ''' Retrieve OAuth tokens from Lookout API - Returns the access_token and the refresh_token - If the access token is already stored, returns the variables stored locally ''' token_json = {} if self.access_token: logging.info("The access token has been found locally") return self.access_token, self.refresh_token logging.info("Could not find an access token, getting one now") try: response = requests.post(self.api_domain + "/oauth/token", data="grant_type=client_credentials", headers=self.header(self.api_key)) try: token_json = json.loads(response.text) except (AttributeError, ValueError) as e: logging.info("Exception when requesting new access token: " + str(e)) logging.info("Refreshing access token...") self.refresh_oauth() if 'access_token' in token_json and 'error' not in token_json: logging.info("Storing creds in Azure Vault") self.access_token = token_json['access_token'] self.refresh_token = token_json['refresh_token'] self.az_kv.set_secret("AccessToken", self.access_token) self.az_kv.set_secret("RefreshToken", self.refresh_token) self.az_kv.set_secret("IsValid", "YES") self.az_kv.set_secret("AuthRetryCounter", 1) logging.info("Got authenticated") return self.access_token, self.refresh_token else: if token_json['error'] and token_json['error'] == 'invalid_client': # Set flag to avoid unwanted retries in case of invalid key/client self.retry_counter = int(self.retry_counter) + 1 self.az_kv.set_secret("AuthRetryCounter", self.retry_counter) if int(self.retry_counter) >= 10: self.az_kv.set_secret("IsValid", "NO") logging.info("Auth API retry count : " + str(self.retry_counter)) logging.info("Error in oauth") logging.info(str(token_json)) return False except requests.exceptions.ProxyError as e: logging.error("Cannot connect to proxy. Remote end closed connection without response") except requests.exceptions.RequestException as e: logging.error(e) def get_events(self): ''' Method to collect events from Metis API - Gets access token and stream position from Azure Vault - Requests events (retries if error HTTP code) - Collect events lists from Metis API, returns full list of events ''' events = [] retry_count = 0 more_events = True if self.is_valid == "NO" or int(self.retry_counter) >= 10: logging.info("Please check API key, Auth API responds with Invalid Client error after 10 retries") return events if not self.access_token: self.get_oauth() if self.access_token: # Added cycle count to avoid long data polling cycle_count = 0 while more_events and retry_count < 10 and cycle_count < 10 : logging.info("Fetching Events from Position {}".format(self.stream_position)) try: response = requests.get(self.api_domain + "/events?eventType=DEVICE,THREAT,AUDIT", headers=self.header(self.access_token), params={"streamPosition": self.stream_position}) if response.status_code == 400 and response.json()['errorCode'] in self.stale_token_errors: self.refresh_oauth() continue elif response.status_code != requests.codes.ok: logging.info("Received error code {}, trying again to get events".format(response.status_code)) retry_count = retry_count + 1 continue except requests.exceptions.ProxyError as e: logging.error("Cannot connect to proxy. Remote end closed connection without response") except requests.exceptions.RequestException as e: logging.error(e) cycle_count = cycle_count + 1 events = events + response.json()['events'] self.stream_position = response.json()['streamPosition'] #update stream position in Azure Vault self.az_kv.set_secret("StreamPosition", self.stream_position) more_events = response.json()['moreEvents'] logging.info("Fetched Event Count {}".format(len(events))) logging.info("More Events to Fetch : {}".format(more_events)) if retry_count >= 10: logging.error("Too many failed attempts to retrieve events, shutting down.") sys.exit(2) return events